#region Namespaces

using System;
using System.Xml;

#endregion

namespace Vibstudio.X4NET.Xml.Linq
{
    internal class XNodeReader : XmlReader, IXmlLineInfo
    {
        private readonly NameTable _nameTable = new NameTable();

        private readonly XNode _start;

        private int _attr = -1;

        private bool _attrValue;

        private bool _endElement;

        private ReadState _state = ReadState.Initial;

        public XNodeReader(XNode node)
        {
            CurrentNode = node;
            _start = node;
        }

        public override int AttributeCount
        {
            get
            {
                if (_state != ReadState.Interactive || _endElement)
                {
                    return 0;
                }
                int i = 0;
                switch (CurrentNode.NodeType)
                {
                    case XmlNodeType.Document:
                        XDeclaration xd = ((XDocument)CurrentNode).Declaration;
                        i = (xd.Version != null ? 1 : 0) +
                            (xd.Encoding != null ? 1 : 0) +
                            (xd.Standalone != null ? 1 : 0);
                        return i;
                    case XmlNodeType.DocumentType:
                        XDocumentType dtd = (XDocumentType)CurrentNode;
                        i = (dtd.PublicId != null ? 1 : 0) +
                            (dtd.SystemId != null ? 1 : 0) +
                            (dtd.InternalSubset != null ? 1 : 0);
                        return i;
                    case XmlNodeType.Element:
                        XElement el = (XElement)CurrentNode;
                        for (XAttribute a = el.FirstAttribute; a != null; a = a.NextAttribute)
                        {
                            i++;
                        }
                        return i;
                }
                return 0;
            }
        }

        public override string BaseURI
        {
            get { return CurrentNode.BaseUri ?? String.Empty; }
        }

        public override int Depth
        {
            get
            {
                if (EOF)
                {
                    return 0;
                }
                int i = 0;

                for (XNode n = CurrentNode.Parent; n != null; n = n.Parent)
                {
                    i++;
                }
                if (_attr >= 0)
                {
                    i++;
                }
                if (_attrValue)
                {
                    i++;
                }
                return i;
            }
        }

        public override bool EOF
        {
            get { return _state == ReadState.EndOfFile || _state == ReadState.Error; }
        }

        public override bool HasAttributes
        {
            get
            {
                if (EOF || _endElement || CurrentNode == null)
                {
                    return false;
                }

                if (CurrentNode is XElement)
                {
                    return ((XElement)CurrentNode).HasAttributes;
                }
                return AttributeCount > 0;
            }
        }

        public override bool HasValue
        {
            get
            {
                if (EOF)
                {
                    return false;
                }
                if (_attr >= 0)
                {
                    return true;
                }
                switch (CurrentNode.NodeType)
                {
                    case XmlNodeType.Element:
                    case XmlNodeType.Document:
                    case XmlNodeType.EndElement:
                        return false;
                    default:
                        return true;
                }
            }
        }

        public override bool IsEmptyElement
        {
            get { return !EOF && _attr < 0 && CurrentNode is XElement && ((XElement)CurrentNode).IsEmpty; }
        }

        public override string LocalName
        {
            get
            {
                object name = GetCurrentName();
                if (name == null)
                {
                    return String.Empty;
                }
                if (name is string)
                {
                    return (string)name;
                }
                return ((XName)name).LocalName;
            }
        }

        public override string NamespaceURI
        {
            get
            {
                XName name = GetCurrentName() as XName;
                if (name != null)
                {
                    return name.LocalName == "xmlns" && name.Namespace == XNamespace.None
                               ? XNamespace.Xmlns.NamespaceName
                               : name.NamespaceName;
                }
                return String.Empty;
            }
        }

        public override XmlNameTable NameTable
        {
            get { return _nameTable; }
        }

        public override XmlNodeType NodeType
        {
            get
            {
                return _state != ReadState.Interactive
                           ? XmlNodeType.None
                           : _endElement
                                 ? XmlNodeType.EndElement
                                 : _attrValue
                                       ? XmlNodeType.Text
                                       : _attr >= 0
                                             ? XmlNodeType.Attribute
                                             : CurrentNode.NodeType == XmlNodeType.Document
                                                   ? XmlNodeType.XmlDeclaration
                                                   : CurrentNode.NodeType;
            }
        }

        public override string Prefix
        {
            get
            {
                XName name = GetCurrentName() as XName;
                if (name == null || name.Namespace == XNamespace.None)
                {
                    return String.Empty;
                }
                XElement el = (CurrentNode as XElement) ?? CurrentNode.Parent;
                if (el == null)
                {
                    return String.Empty;
                }
                return el.GetPrefixOfNamespace(name.Namespace) ?? String.Empty;
            }
        }

        public override ReadState ReadState
        {
            get { return _state; }
        }

        public override string Value
        {
            get
            {
                if (ReadState != ReadState.Interactive)
                {
                    return string.Empty;
                }
                XAttribute a = GetCurrentAttribute();
                if (a != null)
                {
                    return a.Value;
                }
                switch (CurrentNode.NodeType)
                {
                    case XmlNodeType.Document:
                        XDeclaration xd = ((XDocument)CurrentNode).Declaration;
                        if (_attr >= 0)
                        {
                            switch (LocalName)
                            {
                                case "version":
                                    return xd.Version;
                                case "encoding":
                                    return xd.Encoding;
                                default:
                                    return xd.Standalone;
                            }
                        }
                        string s = xd.ToString();
                        return s.Substring(6, s.Length - 6 - 2);
                    case XmlNodeType.DocumentType:
                        XDocumentType dtd = (XDocumentType)CurrentNode;
                        switch (LocalName)
                        {
                            case "PUBLIC":
                                return dtd.PublicId;
                            case "SYSTEM":
                                return dtd.SystemId;
                            default:
                                return dtd.InternalSubset;
                        }
                    case XmlNodeType.ProcessingInstruction:
                        return ((XProcessingInstruction)CurrentNode).Data;
                    case XmlNodeType.CDATA:
                    case XmlNodeType.Text:
                        return ((XText)CurrentNode).Value;
                    case XmlNodeType.Comment:
                        return ((XComment)CurrentNode).Value;
                }
                return String.Empty;
            }
        }

        internal XNode CurrentNode { get; private set; }

        int IXmlLineInfo.LineNumber
        {
            get
            {
                var o = (XObject)GetCurrentAttribute() ?? CurrentNode;
                return o != null ? o.LineNumber : 0;
            }
        }

        int IXmlLineInfo.LinePosition
        {
            get
            {
                var o = (XObject)GetCurrentAttribute() ?? CurrentNode;
                return o != null ? o.LinePosition : 0;
            }
        }

        bool IXmlLineInfo.HasLineInfo()
        {
            var o = (XObject)GetCurrentAttribute() ?? CurrentNode;
            return o != null && ((IXmlLineInfo)o).HasLineInfo();
        }

        internal XAttribute GetCurrentAttribute()
        {
            return GetXAttribute(_attr);
        }

        private XAttribute GetXAttribute(int idx)
        {
            if (EOF)
            {
                return null;
            }
            XElement el = CurrentNode as XElement;
            if (el == null)
            {
                return null;
            }
            int i = 0;
            foreach (XAttribute a in el.Attributes())
            {
                if (i++ == idx)
                {
                    return a;
                }
            }
            return null;
        }

        private object GetCurrentName()
        {
            if (EOF || _attrValue)
            {
                return null;
            }
            return GetName(_attr);
        }

        private object GetName(int attr)
        {
            if (attr >= 0)
            {
                switch (CurrentNode.NodeType)
                {
                    case XmlNodeType.Element:
                        XAttribute a = GetXAttribute(attr);
                        return a.Name;
                    case XmlNodeType.DocumentType:
                        if (attr == 0)
                        {
                            return ((XDocumentType)CurrentNode).PublicId != null ? "PUBLIC" : "SYSTEM";
                        }
                        return "SYSTEM";
                    case XmlNodeType.Document:
                        XDeclaration xd = ((XDocument)CurrentNode).Declaration;
                        switch (attr)
                        {
                            case 0:
                                return xd.Version != null ? "version" : xd.Encoding != null ? "encoding" : "standalone";
                            case 1:
                                return xd.Version != null
                                           ? (xd.Encoding != null ? "encoding" : "standalone")
                                           : "standalone";
                        }
                        return "standalone";
                }
            }
            else
            {
                switch (CurrentNode.NodeType)
                {
                    case XmlNodeType.Document:
                        return "xml";
                    case XmlNodeType.Element:
                        return ((XElement)CurrentNode).Name;
                    case XmlNodeType.ProcessingInstruction:
                        return ((XProcessingInstruction)CurrentNode).Target;
                    case XmlNodeType.DocumentType:
                        return ((XDocumentType)CurrentNode).Name;
                }
            }
            return null;
        }

        public override void Close()
        {
            _state = ReadState.Closed;
        }

        public override string LookupNamespace(string prefix)
        {
            if (EOF)
            {
                return null;
            }
            XElement el = (CurrentNode as XElement) ?? CurrentNode.Parent;
            if (el == null)
            {
                return null;
            }
            var xn = el.GetNamespaceOfPrefix(prefix);
            return xn != XNamespace.None ? xn.NamespaceName : null;
        }

        public override bool MoveToElement()
        {
            if (_attr >= 0)
            {
                _attrValue = false;
                _attr = -1;
                return true;
            }
            return false;
        }

        public override bool MoveToFirstAttribute()
        {
            if (AttributeCount > 0)
            {
                _attr = 0;
                _attrValue = false;
                return true;
            }
            return false;
        }

        public override bool MoveToNextAttribute()
        {
            int c = AttributeCount;
            if (_attr + 1 < c)
            {
                _attr++;
                _attrValue = false;
                return true;
            }
            return false;
        }

        public override bool MoveToAttribute(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            int c = AttributeCount;
            bool match = false;
            for (int i = 0; i < c; i++)
            {
                object o = GetName(i);
                if (o == null)
                {
                    continue;
                }
                if ((o as string) == name)
                {
                    match = true;
                }
                XName n = (XName)o;
                if (name.EndsWith(n.LocalName, StringComparison.Ordinal) && name == GetPrefixedName((XName)o))
                {
                    match = true;
                }
                if (match)
                {
                    _attr = i;
                    _attrValue = false;
                    return true;
                }
            }
            return false;
        }

        private string GetPrefixedName(XName name)
        {
            XElement el = (CurrentNode as XElement) ?? CurrentNode.Parent;
            if (el == null ||
                name.Namespace == XNamespace.None ||
                el.GetPrefixOfNamespace(name.Namespace) == String.Empty)
            {
                return name.LocalName;
            }
            return String.Concat(el.GetPrefixOfNamespace(name.Namespace), ":", name.LocalName);
        }

        public override bool MoveToAttribute(string local, string ns)
        {
            if (local == null)
            {
                throw new ArgumentNullException("local");
            }
            if (ns == null)
            {
                throw new ArgumentNullException("ns");
            }

            int c = AttributeCount;
            bool match = false;
            for (int i = 0; i < c; i++)
            {
                object o = GetName(i);
                if (o == null)
                {
                    continue;
                }
                if ((o as string) == local && ns.Length == 0)
                {
                    match = true;
                }
                XName n = (XName)o;
                if (local == n.LocalName && ns == n.NamespaceName)
                {
                    match = true;
                }
                if (match)
                {
                    _attr = i;
                    _attrValue = false;
                    return true;
                }
            }
            return false;
        }

        public override string GetAttribute(int i)
        {
            int a_bak = _attr;
            bool av_bak = _attrValue;
            try
            {
                MoveToElement();
                MoveToAttribute(i);
                return Value;
            }
            finally
            {
                _attr = a_bak;
                _attrValue = av_bak;
            }
        }

        public override string GetAttribute(string name)
        {
            int a_bak = _attr;
            bool av_bak = _attrValue;
            try
            {
                MoveToElement();
                return MoveToAttribute(name) ? Value : null;
            }
            finally
            {
                _attr = a_bak;
                _attrValue = av_bak;
            }
        }

        public override string GetAttribute(string local, string ns)
        {
            int a_bak = _attr;
            bool av_bak = _attrValue;
            try
            {
                MoveToElement();
                return MoveToAttribute(local, ns) ? Value : null;
            }
            finally
            {
                _attr = a_bak;
                _attrValue = av_bak;
            }
        }

        public override bool Read()
        {
            _attr = -1;
            _attrValue = false;

            switch (_state)
            {
                case ReadState.Initial:
                    _state = ReadState.Interactive;
                    XDocument doc = CurrentNode as XDocument;
                    if (doc != null)
                    {
                        if (doc.Declaration != null)
                        {
                            return true;
                        }
                    }
                    else
                    {
                        return true;
                    }
                    break;
                case ReadState.Interactive:
                    break;
                default:
                    return false;
            }

            if (CurrentNode is XDocument)
            {
                XDocument doc = CurrentNode as XDocument;
                CurrentNode = doc.FirstNode;
                if (CurrentNode == null)
                {
                    _state = ReadState.EndOfFile;
                    return false;
                }
                CurrentNode = doc.FirstNode;
                return true;
            }

            XElement c = CurrentNode as XElement;
            if (c != null && !_endElement)
            {
                if (c.FirstNode != null)
                {
                    CurrentNode = c.FirstNode;
                    return true;
                }
                if (!c.IsEmpty)
                {
                    _endElement = true;
                    return true;
                }
            }
            _endElement = false;
            if (CurrentNode.NextNode != null && CurrentNode != _start)
            {
                CurrentNode = CurrentNode.NextNode;
                return true;
            }
            if (CurrentNode.Parent == null || CurrentNode == _start)
            {
                _state = ReadState.EndOfFile;
                return false;
            }
            CurrentNode = CurrentNode.Parent;
            _endElement = true;
            return true;
        }

        public
            override
            bool ReadAttributeValue()
        {
            if (_attr < 0 || _attrValue)
            {
                return false;
            }
            _attrValue = true;
            return true;
        }

        public override void ResolveEntity()
        {
            throw new NotSupportedException();
        }
    }
}